文章摘要
在使用 Netty 进行客户端和服务端开发时,需要对 serverBootStrap 的 option 和 childOption 进行自定义的一些配置,具体的 Channel 配置参数可以查看 ChannelOption 里面声明的常量,按使用场景可以划分为 Netty 通用参数、Socket 套接字参数以及 IP 参数。
源码
以下代码就是在实际项目中一个 Netty server 的具体 ChannelOption 参数配置:
1 | serverBootstrap = serverBootstrap.childHandler(new ListInitializer()) |
ChannelOption 源码中定义的参数如下,当前使用的版本为 netty-all-4.1.29.Final:
1 | public static final ChannelOption<ByteBufAllocator> ALLOCATOR = valueOf("ALLOCATOR"); |
源码中可以看到,参数MAX_MESSAGES_PER_READ
、WRITE_BUFFER_HIGH_WATER_MARK
、WRITE_BUFFER_LOW_WATER_MARK
、DATAGRAM_CHANNEL_ACTIVE_ON_REGISTRATION
已经弃用,下面简单的整理一下 ChannelOption 中参数的使用方法。
使用
Netty 通用参数
- ChannelOption.ALLOCATOR
ByteBuf 的分配器,默认值为 ByteBufAllocator.DEFAULT,在 4.0 版本中,默认的 allocator 为 UnpooledByteBufAllocator,4.1版本中增强了缓冲区泄漏追踪机制,默认的 allocator 就改为了 PooledByteBufAllocator。即 4.1 版本使用对象池,实现了重用缓冲区(可以直接只用这个配置)。
1 | bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT); |
- ChannelOption.RCVBUF_ALLOCATOR
用于 Channel 分配接受 Buffer 的分配器,默认值为 AdaptiveRecvByteBufAllocator.DEFAULT,是一个自适应的接受缓冲区分配器,能根据接受到的数据自动调节大小。可选值为FixedRecvByteBufAllocator,固定大小的接受缓冲区分配器。
- ChannelOption.MESSAGE_SIZE_ESTIMATOR
消息大小估算器,默认为 DefaultMessageSizeEstimator.DEFAULT。估算 ByteBuf、ByteBufHolder 和 FileRegion 的大小,其中 ByteBuf 和 ByteBufHolder 为实际大小,FileRegion 估算值为0。该值估算的字节数在计算水位时使用,FileRegion 为0可知FileRegion 不影响高低水位。
- ChannelOption.CONNECT_TIMEOUT_MILLIS
连接超时毫秒数,默认值 30000 毫秒。
- ChannelOption.MAX_MESSAGES_PER_READ
【弃用,使用 MaxMessagesRecvByteBufAllocator】一次 Loop 读取的最大消息数,对于 ServerChannel 或者 NioByteChannel,默认值为 16,其他 Channel 默认值为1。默认值这样设置,是因为:ServerChannel 需要接受足够多的连接,保证大吞吐量,NioByteChannel 可以减少不必要的系统调用 select。
- ChannelOption.WRITE_SPIN_COUNT
一个 Loop 写操作执行的最大次数,默认值为 16。也就是说,对于大数据量的写操作至多进行 16 次,如果 16 次仍没有全部写完数据,此时会提交一个新的写任务给 EventLoop,任务将在下次调度继续执行。这样,其他的写请求才能被响应不会因为单个大数据量写请求而耽误。
- ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK
【弃用,使用 #WRITE_BUFFER_WATER_MARK】写高水位标记,默认值 64 KB。如果 Netty 的写缓冲区中的字节超过该值,Channel 的 isWritable() 返回 False。
- ChannelOption.WRITE_BUFFER_LOW_WATER_MARK
【弃用,使用 #WRITE_BUFFER_WATER_MARK】写低水位标记,默认值 32 KB。当 Netty 的写缓冲区中的字节超过高水位之后若下降到低水位,则 Channe 的 isWritable() 返回 True。写高低水位标记使用户可以控制写入数据速度,从而实现流量控制。推荐做法是:每次调用 channl.write(msg) 方法首先调用 channel.isWritable() 判断是否可写。
- ChannelOption.WRITE_BUFFER_WATER_MARK
写水位标记,使用方法见 WRITE_BUFFER_HIGH_WATER_MARK
和 WRITE_BUFFER_LOW_WATER_MARK
。
- ChannelOption.ALLOW_HALF_CLOSURE
一个连接的远端关闭时本地端是否关闭,默认值为 False。值为 False 时,连接自动关闭;为 True 时,触发 ChannelInboundHandler 的 userEventTriggered() 方法,事件为 ChannelInputShutdownEvent。
- ChannelOption.AUTO_READ
自动读取,默认值为 True。Netty 只在必要的时候才设置关心相应的 I/O 事件。对于读操作,需要调用 channel.read() 设置关心的 I/O 事件为 OP_READ,这样若有数据到达才能读取以供用户处理。该值为 True 时,每次读操作完毕后会自动调用 channel.read(),从而有数据到达便能读取;否则,需要用户手动调用 channel.read()。需要注意的是:当调用 config.setAutoRead(boolean) 方法时,如果状态由 false 变为 true,将会调用 channel.read() 方法读取数据;由 true 变为 false,将调用 config.autoReadCleared() 方法终止数据读取。
- ChannelOption.AUTO_CLOSE
自动关闭,默认值为 True。如果 True,则 Channel 在写入失败时立即自动关闭。
- ChannelOption.DATAGRAM_CHANNEL_ACTIVE_ON_REGISTRATION
【弃用】DatagramChannel 注册的 EventLoop 即表示已激活。
- ChannelOption.SINGLE_EVENTEXECUTOR_PER_GROUP
单线程执行 ChannelPipeline 中的事件,默认值为 True。该值控制执行 ChannelPipeline 中执行 ChannelHandler 的线程。如果为 True,整个 pipeline 由一个线程执行,这样不需要进行线程切换以及线程同步,是 Netty4 的推荐做法;如果为 False,ChannelHandler 中的处理过程会由 Group 中的不同线程执行。
Socket 套接字参数
- ChannelOption.SO_BROADCAST
对应套接字选项中的 SO_BROADCAST,设置广播模式。允许或禁止发送广播数据,只有数据报套接字支持广播,并且是在支持广播消息的网络上才能使用。
- ChannelOption.SO_KEEPALIVE
对应套接字选项中的 SO_KEEPALIVE,是否启动心跳包活机制,默认值为 False。该参数用于设置 TCP 连接,当设置该选项以后,TCP 会主动测试这个连接的状态,如果该连接长时间没有数据交流,TCP 会自动发送一个活动探测数据报文(keep-alive probe),来检测连接是否存活。可以将此功能视为TCP的心跳机制,需要注意的是:默认的心跳间隔是 7200s 即 2 小时。
- ChannelOption.SO_SNDBUF
对应套接字选项中的 SO_SNDBUF,TCP数据接收缓冲区大小。接收缓冲区即 TCP 接收滑动窗口,用于保存网络协议站内收到的数据,直到应用程序读取成功。Linux操作系统可使用命令: cat /proc/sys/net/ipv4/tcp_rmem 查询其大小。一般情况下,该值可由用户在任意时刻设置,但当设置值超过 64KB 时,需要在连接到远端之前设置。
联系 SO_RCVBUF 参数来看,每个套接字都有一个发送缓冲区和一个接收缓冲区,TCP 设置这个两个选项注意顺序:对于客户端必须在调用 connect 之前,对于服务器端应该在调用 listen 之前,因为窗口选项是在建立连接时用 syn 分节与对端互换得到的。
TCP 套接字的缓冲区大小至少应该是 MSS 的4倍;MSS=MTU-40 头部,一般以太网卡 MTU 是1500;典型缓冲区默认大小是 8192 字节或者更大。对于一次发送大量数据,可以增加到48K,64K等,为了达到最佳性能,缓冲区可能至少要与BDP(带宽延迟乘积)一样大小。在实际的过程中发送数据和接收数据量比较大,提高接收缓冲区能够减少发送端的阻塞,避免 send()、recv() 不断的循环收发。
- ChannelOption.SO_RCVBUF
对应于套接字选项中的SO_RCVBUF,TCP数据发送缓冲区大小。发送缓冲区即 TCP 发送滑动窗口,用于保存发送数据直到发送成功。Linux 操作系统可使用命令:cat /proc/sys/net/ipv4/tcp_smem 查询其大小。
- ChannelOption.SO_REUSEADDR
对应套接字选项中的 SO_REUSEADDR,这个参数表示允许重复使用本地地址和端口,默认值False。例如,某个服务占用了 TCP 的 8080 端口,其他服务再对这个端口进行监听就会报错, SO_REUSEADDR 这个参数就是用来解决这个问题的,该参数允许服务公用一个端口,这个在服务器程序中比较常用,例如某个进程非正常退出,对一个端口的占用可能不会立即释放,这时候如果不设置这个参数,其他进程就不能立即使用这个端口。而设置 SO_REUSEADDR,就允许在同一个端口上启动同一服务器的多个实例,只要每个实例绑定不同的本地地址即可。
- ChannelOption.SO_LINGER
对应套接字选项中的 SO_LINGER,关闭Socket的延迟时间,默认值为-1,表示禁用该功能。Linux内核默认的处理方式是当用户调用 close() 方法的时候,函数返回,在可能的情况下,尽量发送数据,不一定保证会发生剩余的数据,造成了数据的不确定性,使用 SO_LINGER 可以阻塞 close() 的调用时间,直到数据完全发送。
具体配置方式:-1 表示 socket.close() 方法立即返回,但 OS 底层会将发送缓冲区全部发送到对端。0 表示 socket.close() 方法立即返回,OS 放弃发送缓冲区的数据直接向对端发送 RST 包,对端收到复位错误。非 0 整数值表示调用 socket.close() 方法的线程被阻塞直到延迟时间到或发送缓冲区中的数据发送完毕,若超时,则对端会收到复位错误。
- ChannelOption.SO_BACKLOG
对应的是 TCP/IP 协议 listen 函数中的 backlog 参数,函数 listen(int socketfd,int backlog) 用来初始化服务端可连接队列。服务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接,多个客户端来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理,backlog 参数指定了用于临时存放已完成三次握手的请求队列的大小。如果未设置或者设置值为小于 1,则 Java 将会使用默认值50。
- ChannelOption.SO_TIMEOUT
用于设置接受数据的等待的超时时间,单位为毫秒,默认值为 0,表示无限等待。
- ChannelOption.TCP_NODELAY
对应于套接字选项中的 TCP_NODELAY,该参数的使用与 Nagle 算法有关,默认情况其是启用的。
在 TCP/IP 协议中,无论发送多少数据,总是需要在数据前面加上协议头,同时,对方接收到数据,也需要发送 ACK 表示确认。为了尽可能地利用网络宽带,TCP 总是希望尽可能的发送足够大的数据。Nagle 算法的目的就是为了尽可能的发送大块数据,避免网络中充斥这小块数据。虽然该方式有效提高网络的有效负载,但是却造成了延时。
TCP_NODELAY 就是用于关闭 Nagle 算法。如果要求高实时性,有数据发送时就马上发送,就将该选项设置为 true 关闭 Nagle 算法;如果要减少发送次数减少网络交互,就设置为 false 启用 Nagle 算法,等累积一定大小后再发送。
IP 参数
- ChannelOption.IP_TOS
IP 参数,设置 IP 头部的 Type-of-Service 字段,用于描述 IP 包的优先级和 QoS 选项。
- ChannelOption.IP_MULTICAST_ADDR
对应 IP 参数 IP_MULTICAST_IF,设置对应地址的网卡为多播模式。
- ChannelOption.IP_MULTICAST_IF
对应 IP 参数 IP_MULTICAST_IF2,同上但支持 IPV6。
- ChannelOption.IP_MULTICAST_TTL
IP 参数,多播数据报的 time-to-live 即存活跳数。
- ChannelOption.IP_MULTICAST_LOOP_DISABLED
对应 IP 参数 IP_MULTICAST_LOOP,设置本地回环接口的多播功能。由于 IP_MULTICAST_LOOP返回 True 表示关闭,所以 Netty 加上后缀 _DISABLED 防止歧义。
附: